# 1.4 Prompt 工程

> **Week 1 | Lesson 1.4 | 30 分钟**

---

## 学习目标

完成本课后，你将能够：

- 掌握编写高质量系统提示词（System Prompt）的 5 条黄金规则
- 为每一条规则说出反例和改进方法
- 将模糊的提示词改写为精确、可执行的指令
- 在 Agent 代码中应用优化后的 Prompt

---

## 什么是 System Prompt？

System Prompt 是你给 LLM 的"角色设定 + 行为规范"。它告诉模型：**你是谁、你应该做什么、你不应该做什么、你应该怎么输出**。

打个比方：如果你雇了一个能力很强的新人，但不给他任何工作说明，他大概率做不出你想要的结果。System Prompt 就是那份"工作说明书"。

```python
# 不好的 System Prompt
system_prompt = "你是一个助手。"

# 好的 System Prompt
system_prompt = (
    "你是一个专业的数据分析助手，擅长用 Python 处理表格数据。"
    "你的回答必须包含：1) 分析结论 2) 关键数据 3) Python 代码。"
    "如果数据不足以得出结论，请明确说明，不要编造数据。"
    "只回答与数据分析相关的问题，其他问题请拒绝回答。"
)
```

---

## 5 条黄金规则

### 规则 1：明确角色定位（Be Specific About Role）

让模型清楚地知道"它是谁"、"它擅长什么"。

**不好的例子：**
```
你是一个助手。
```

**为什么不好：**
- "助手"太宽泛，模型不知道该表现得像一个程序员、翻译、还是客服
- 没有专业领域的限定

**好的例子：**
```
你是一个有 10 年经验的 Python 后端工程师，擅长 Web API 开发和数据库设计。
你用简洁、实用的方式回答问题，代码遵循 PEP 8 规范。
```

**对比效果：**

| 提问 | "你是一个助手" 的回答 | "你是 Python 后端工程师" 的回答 |
|------|----------------------|-------------------------------|
| "怎么存用户数据？" | 模糊的通用回答 | 会提到数据库选型、ORM、迁移、索引优化 |

**代码示例：**

```python
# prompt_demo.py
from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY", "")

client = OpenAI(
    base_url="http://localhost:11434/v1" if api_key == "ollama" else None,
    api_key="ollama" if api_key == "ollama" else api_key,
)
MODEL = "qwen2.5:7b" if api_key == "ollama" else "gpt-4o-mini"


def test_prompt(system_prompt: str, user_question: str) -> str:
    """测试不同的 system prompt 效果"""
    response = client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_question},
        ],
        max_tokens=200,
    )
    return response.choices[0].message.content


# 对比测试
question = "我应该怎么处理用户登录？"

# 版本 A：模糊的角色
print("=== 版本 A（模糊角色）===")
result_a = test_prompt("你是一个助手。", question)
print(result_a)
print()

# 版本 B：明确的角色
print("=== 版本 B（明确角色）===")
result_b = test_prompt(
    "你是一个有 10 年经验的 Web 安全专家，擅长身份认证和会话管理。"
    "你总是从安全角度出发，优先考虑防护措施。",
    question,
)
print(result_b)
```

**核心要点：角色越具体，回答越专业。**

---

### 规则 2：定义输出格式（Define Output Format）

明确告诉模型你希望它**怎么回答**，包括结构、长度、语言等。

**不好的例子：**
```
介绍一下机器学习。
```

**为什么不好：**
- 模型不知道你要多长、什么格式、什么深度的内容
- 可能给你 50 字的概述，也可能给你 2000 字的论文

**好的例子：**
```
请用以下格式介绍机器学习：

1. 一句话定义（不超过 30 字）
2. 三个主要应用场景（每个场景一句话）
3. 一个初学者可以上手的工具（含安装命令）
4. 下一步学习建议（3 条以内）

用中文回答，总字数不超过 300 字。
```

**代码示例：**

```python
# 在 Agent 中强制输出格式
def analyze_data_prompt(csv_description: str) -> list:
    """生成结构化的数据分析 Prompt"""
    return [
        {"role": "system", "content": (
            "你是一个数据分析师。请严格按照以下 JSON 格式输出：\n"
            "{\n"
            '  "summary": "数据总体描述（一句话）",\n'
            '  "key_findings": ["发现1", "发现2", "发现3"],\n'
            '  "recommendation": "建议（一句话）"\n'
            "}"
        )},
        {"role": "user", "content": f"分析以下数据: {csv_description}"},
    ]

# 使用示例
messages = analyze_data_prompt("一个电商网站的用户购买记录，包含日期、用户ID、金额")
response = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    # 如果模型支持，可以强制 JSON 输出
    response_format={"type": "json_object"},
)
print(response.choices[0].message.content)
```

**核心要点：不要指望模型"猜"你想要的格式，直接说清楚。**

---

### 规则 3：给示例（Give Examples）

Few-shot prompting（少样本提示）是最有效的提升输出质量的方法之一。给模型 1-3 个"输入 → 期望输出"的例子，效果会有质的飞跃。

**不好的例子：**
```
把这段话改成正式邮件格式：
"老王，那个项目的事再说说，周五之前给我结果"
```

**好的例子（带示例）：**
```
请将口语化的消息改写为正式的商务邮件格式。

示例：
输入: "小李，报表赶紧做一下，明天要"
输出: "尊敬的李经理，您好！关于本月的数据报表，恳请您尽快完成，\n"
      "并于明日提交。如有任何问题，请随时与我联系。谢谢！"

输入: "老王，那个项目的事再说说，周五之前给我结果"
输出:
```

**代码示例：**

```python
# few_shot_agent.py
"""使用 Few-shot Prompt 的 Agent"""

SYSTEM_PROMPT = """你是一个文本格式转换器，将口语化的消息转换为正式的商务邮件。

请遵循以下示例风格：

示例 1:
输入: "老板，我要请假，家里有事，请三天"
输出: "尊敬的领导，您好！因家中有事，特向您申请事假三天，望批准为盼。谢谢！"

示例 2:
输入: "客户那边方案不满意，让咱们改改"
输出: "关于客户反馈的方案调整事宜，我们需要根据客户的具体意见进行优化，
并及时提交新版本。请相关同事尽快处理。谢谢！"

规则:
- 使用正式、礼貌的语言
- 保持简洁
- 添加合适的问候和结束语
"""

def convert_to_email(casual_text: str) -> str:
    """将口语消息转换为正式邮件"""
    response = client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": casual_text},
        ],
        max_tokens=150,
    )
    return response.choices[0].message.content


# 测试
test_messages = [
    "这个项目做不完，人手不够，得加人",
    "周五的会改到周三了，大家记得来",
]

for msg in test_messages:
    print(f"输入: {msg}")
    result = convert_to_email(msg)
    print(f"输出: {result}")
    print("-" * 50)
```

**核心要点：给例子比写描述更有效。模型善于模仿。**

---

### 规则 4：设置约束（Set Constraints）

明确限定模型的行为边界：字数、格式、风格、范围等。

**不好的例子：**
```
写一个产品介绍。
```

**为什么不好：**
- 没有长度限制 → 可能写 2000 字
- 没有目标读者限定 → 可能用专业术语
- 没有风格限定 → 可能很无聊

**好的例子：**
```
写一段产品介绍，面向完全没有技术背景的普通消费者。
- 长度：100-150 字
- 语气：热情、亲切
- 禁止使用任何技术术语（如 API、云端、算法等）
- 必须包含一个生活中的类比
- 以一个问题结尾，引导读者思考
```

**在 Agent 中应用约束：**

```python
def create_constrainted_agent(task: str, constraints: list) -> str:
    """创建带约束的 Agent Prompt"""

    # 将约束条件格式化为清晰的列表
    constraint_text = "\n".join(f"- {c}" for c in constraints)

    system_prompt = (
        f"你是一个专业助手。\n\n"
        f"你的任务: {task}\n\n"
        f"你必须严格遵守以下约束:\n"
        f"{constraint_text}\n\n"
        f"违反任何一条约束的回答都是不合格的。"
    )

    return system_prompt


# 使用示例
prompt = create_constrainted_agent(
    task="写一段 Python 函数，判断一个数是否为质数",
    constraints=[
        "函数名必须是 is_prime",
        "输入参数类型为 int，返回值类型为 bool",
        "包含类型检查和异常处理",
        "不超过 20 行代码",
        "附带 3 个测试用例（使用 assert）",
        "不要使用第三方库",
    ],
)

response = client.chat.completions.create(
    model=MODEL,
    messages=[{"role": "system", "content": prompt},
              {"role": "user", "content": "请完成任务"}],
    max_tokens=500,
)
print(response.choices[0].message.content)
```

**核心要点：约束越具体，输出越可控。把"不要 XX"写成明确的规则。**

---

### 规则 5：告诉它不要做什么（Tell It What NOT to Do）

明确禁止的行为和期望的行为一样重要。这能大幅减少"幻觉"（编造信息）和不相关的内容。

**不好的例子：**
```
帮我写一份市场分析。
```

**好的例子：**
```
帮我写一份市场分析。

⚠️ 注意事项（请务必遵守）:
- 不要编造任何数据或统计数据
- 如果某个信息你不确定，请说"暂无可靠数据"
- 不要包含任何投资建议
- 不要使用"可能"、"或许"等模糊词汇，除非确实不确定
- 不要超过 500 字
- 不要讨论与市场分析无关的话题
```

**代码示例：**

```python
def safe_answer_prompt(question: str) -> str:
    """创建一个安全的问答 Prompt，防止模型编造信息"""

    system_prompt = (
        "你是一个诚实的知识助手。请严格遵守以下规则:\n\n"
        "✅ 你应该做的:\n"
        "- 用简洁的中文回答问题\n"
        "- 如果不确定，明确说明你不确信\n"
        "- 给出你的判断依据（如果有的话）\n\n"
        "❌ 你不应该做的:\n"
        "- 不要编造任何事实、数据或引用\n"
        "- 不要猜测你不确定的人名、日期或数字\n"
        "- 不要给出医疗、法律或金融方面的专业建议\n"
        "- 不要用'据研究表明'这样的模糊引用\n"
        "- 如果问题超出你的知识范围，直接承认不知道"
    )

    return client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": question},
        ],
        max_tokens=300,
    ).choices[0].message.content


# 测试：问一个模型可能不知道的事
print(safe_answer_prompt("2025 年 3 月发生了什么重大新闻？"))
# 如果模型的训练数据不包含这个信息，好的 prompt 会让它说"我不知道"
# 而不是编造一个故事
```

**核心要点：防止幻觉（Hallucination）最有效的方法之一，就是明确告诉模型不要编造。**

---

## 5 条规则速查表

| 规则 | 一句话 | 关键问题 |
|------|--------|----------|
| 1. 明确角色 | "你是谁？" | 回答是否足够具体和专业？ |
| 2. 定义格式 | "怎么输出？" | 有没有指定结构、长度、类型？ |
| 3. 给示例 | "做给我看" | 有没有 1-3 个输入输出示例？ |
| 4. 设置约束 | "边界在哪？" | 有没有明确限定范围？ |
| 5. 告知禁忌 | "别做什么？" | 有没有列出禁止的行为？ |

---

## 综合实战：从烂 Prompt 到好 Prompt

### 场景

你正在构建一个客服 Agent，需要它处理用户投诉。

### 版本 1（差）

```
你是一个客服。处理用户投诉。
```

**问题：**
- 角色不明确（什么行业的客服？）
- 没有输出格式
- 没有示例
- 没有约束
- 没有禁止事项

### 版本 2（及格）

```
你是一个电商平台的客服专员。当用户投诉时，你需要：
1. 表达歉意
2. 了解问题详情
3. 给出解决方案
4. 语气友好
```

**改进：** 有了角色和步骤，但仍然缺少格式、示例和约束。

### 版本 3（优秀）

```python
CUSTOMER_SERVICE_PROMPT = """
你是一个有 5 年经验的电商平台客服专员，擅长处理售后投诉。

📋 回复格式（请严格遵守）:
---
尊敬的 [用户称呼]，您好！

【问题确认】[复述用户的问题，表示理解]

【解决方案】[给出 1-2 个具体的解决方案]

【后续跟进】[说明下一步操作和时间]

如有任何疑问，请随时联系我们。祝您生活愉快！
---

📝 示例:
用户输入: "我买的衣服尺码不对，太紧了，我要退货"
你的回复:
---
尊敬的顾客，您好！

【问题确认】非常抱歉给您带来了不便。我了解到您购买的衣服尺码偏小，需要办理退货。

【解决方案】我们为您提供以下方案：
1. 全额退款退货：您可以将商品寄回，我们承担运费，款项将在 3-5 个工作日退回
2. 换货：我们为您更换合适的尺码，同样免运费

【后续跟进】请您在订单页面申请退货或换货，我们将在收到退回商品后的 24 小时内处理。

如有任何疑问，请随时联系我们。祝您生活愉快！
---

⚠️ 注意事项:
- 语气始终保持礼貌、耐心、专业
- 不要承诺超出权限的内容（如"一定给你免单"）
- 不要与用户争论或反驳
- 如果不确定解决方案，说"我会帮您确认"而不是编造答案
- 回复必须严格按照上面的格式
- 总字数不超过 200 字
"""

# 在 Agent 中使用
def handle_complaint(user_complaint: str) -> str:
    response = client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": CUSTOMER_SERVICE_PROMPT},
            {"role": "user", "content": user_complaint},
        ],
        max_tokens=300,
    )
    return response.choices[0].message.content


# 测试
complaint = "我买的那个手机壳收到是坏的，根本装不上，太生气了！"
print(handle_complaint(complaint))
```

**这个版本：**
- 角色具体到"5 年经验的电商客服"
- 格式用分隔线标出，有明确的字段
- 给了完整的输入输出示例
- 列出了 6 条注意事项（约束 + 禁忌）

---

## 动手练习

### 练习 1：用 5 条规则重写 Prompt

以下是一个质量很差的 Prompt，请用 5 条规则逐条改进：

```
帮我写代码。
```

要求改进后的 Prompt：
- 指定编程语言和任务类型
- 指定输出格式（包含函数签名、注释、测试用例）
- 给一个示例
- 设置代码风格和长度约束
- 列出不要做的事情

### 练习 2：为搜索 Agent 编写更好的 System Prompt

回到 1.3 课的第一个 Agent，它的 system prompt 是这样的：

```python
{"role": "system", "content": "你是一个知识渊博的助手。"}
```

请用 5 条规则改进它，让它：
- 更好地决定何时使用搜索工具
- 在找不到信息时给出合适的回应
- 用中文回答
- 回答简洁（不超过 100 字）
- 不编造不存在的信息

### 练习 3：A/B 测试

写两个版本的 prompt，同一个问题分别运行，对比结果质量：

```python
def ab_test(question: str):
    """A/B 测试两个 prompt 版本"""

    version_a = "回答这个问题。"  # 差版本

    version_b = (  # 好版本
        "请用中文简明扼要地回答以下问题（不超过 100 字）。"
        "如果你不确定答案，请说'我无法确定'。"
        "不要编造任何信息。"
    )

    for label, prompt in [("A（差版本）", version_a), ("B（好版本）", version_b)]:
        response = client.chat.completions.create(
            model=MODEL,
            messages=[
                {"role": "system", "content": prompt},
                {"role": "user", "content": question},
            ],
            max_tokens=200,
        )
        print(f"=== {label} ===")
        print(response.choices[0].message.content)
        print()


# 测试
ab_test("量子计算机和传统计算机的根本区别是什么？")
```

---

## 本节总结

- **Prompt 是 Agent 最重要的"代码"**——它直接决定了模型的行为质量
- **5 条黄金规则**：
  1. **明确角色**——让模型知道它是谁、擅长什么
  2. **定义格式**——指定输出结构、长度、类型
  3. **给示例**——模型善于模仿，1-3 个例子胜过千言万语
  4. **设置约束**——限定行为边界
  5. **告知禁忌**——防止幻觉和不相关输出
- 写 Prompt 的秘诀：**像给新员工写工作说明书一样思考**
- 好的 Prompt 可以显著减少 Agent 的错误行为，提升输出一致性

Week 1 的所有课程到此结束！你已经了解了 Agent 的概念、搭建好环境、构建了第一个 Agent，并学会了如何写好 Prompt。接下来，我们将深入更复杂的 Agent 应用场景。
